package com.example.nio.network.selector;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @author renyitong
 * @version 1.0.0
 * @createTime 2023年08月07日
 * @Description selector选择器服务器端
 */
public class SelectorServer {

    public static void main(String[] args) {
        try (ServerSocketChannel serverChannel = ServerSocketChannel.open()) {
            // 开启一个selector，这玩意最后也是要关闭的
            Selector selector = Selector.open();
            // 给serverChannel绑定端口
            serverChannel.bind(new InetSocketAddress(9000));
            // 要使用选择器进行操作，就必须设置为accept非阻塞的方式，这样的话才能实现改高效利用
            serverChannel.configureBlocking(false);
            // 将选择器注册到ServerSocketChannel中，后面是选择需要监听的事件，只有发生对应事件时才会进行选择，多个事件用 | 连接
            // todo 注意，并不是所有的Channel都支持以下全部四个事件，可能只支持部分
            // 因为是ServerSocketChannel , 这里我们就监听accept就可以了，等待客户端连接
            // SelectionKey.OP_CONNECT --- 连接就绪事件，表示客户端与服务器的连接已经建立成功
            // SelectionKey.OP_ACCEPT --- 接收连接事件，表示服务器监听到了客户连接，服务器可以接收这个连接了
            // SelectionKey.OP_READ --- 读 就绪事件，表示通道中已经有了可读的数据，可以执行读操作了
            // SelectionKey.OP_WRITE --- 写 就绪事件，表示已经可以向通道写数据了（这玩意比较特殊，一般情况下因为都是可以写入的，所以可能会无限循环）
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);

            // todo 为了解决粘包和粘包的问题，定义缓冲区的大小 只有接收了规定的字节之后，作为一个完整的数据包进行处理。
            ByteBuffer buffer  = ByteBuffer.allocate(30);
            // 无限循环等待客户端的操作
            while (true){
                // 每次选择都可能选出多个已经就绪的网络操作，没有操作时它会被阻塞
                int count = selector.select();
                System.out.println("监听到 "+count+" 个事件");
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    // 根据key得到的不同的事件，再去处理不同的操作即可
                    if (key.isAcceptable()){
                        SocketChannel socketChannel = serverChannel.accept();
                        System.out.println("客户端已连接，IP地址为：" + socketChannel.getRemoteAddress());
                        //现在连接就建立好了，接着我们需要将连接注册到选择器，比如我们需要当这个连接有内容可读时就进行处理，注册前先将read设置为非阻塞
                        socketChannel.configureBlocking(false);
                        // 将socketChannel注册到selector上，监听读事件
                        socketChannel.register(selector,SelectionKey.OP_READ);
                        // 这样就在连接建立时，完成了注册，这回合仅此而已。
                    }else if (key.isReadable()){ // 如果当前链接存在可读的数据时，那么就开始处理
                        SocketChannel socketChannel = (SocketChannel)  key.channel();
                        if (socketChannel.read(buffer) < 0){
                            System.out.println("客户端已经断开连接了："+socketChannel.getRemoteAddress());
                            socketChannel.close();   //直接关闭此通道
                            continue;   //继续进行选择
                        }

                        if(buffer.remaining() == 0) {
                            // 修改limit值，当前写到哪里，下次读的最终位置就是这里，limit的作用开始慢慢体现了
                            buffer.flip();
                            System.out.println("接收到客户端数据："+new String(buffer.array(), 0, buffer.remaining()));
                            buffer.clear();
                        }

                        int size = buffer.position();
                        //直接向通道中写入数据就行
                        socketChannel.write(ByteBuffer.wrap(("已收到 "+size+" 字节的数据！").getBytes()));
                    }

                    //处理完成后，一定记得移出迭代器，不然下次还有
                    iterator.remove();
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

}
